AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "shared.lua" )
AddCSLuaFile( "player_meta.lua" )
AddCSLuaFile( "entity_meta.lua" )
AddCSLuaFile( "sh_hooks.lua" )
AddCSLuaFile( "synched_vars.lua" )
AddCSLuaFile( "framebuffer.lua" )
AddCSLuaFile( "cl_text_capture.lua" )
AddCSLuaFile( "blocks.lua" )
AddCSLuaFile( "weapons.lua" )
AddCSLuaFile( "file_reload.lua" )
AddCSLuaFile( "cl_render.lua" )

include( "shared.lua" )
include( "sv_hooks.lua" )

function GM:Initialize()
	syncvar.Set( "waiting", true )
end

function GM:InitPostEntity()
	GAMEMODE:LoadBlocks()
	GAMEMODE:SetUpCheckpoints()
end

function GM:PlayerInitialSpawn( pl )
	pl:SetModel( player_manager.TranslatePlayerModel( GetConVarString( "cl_playermodel" ) ) )
	//pl:SetWeapon( "smg" )
	pl:SetWeapon( "rocketlauncher" )
	pl:SetMana( 0 )
	
	local optimism = ""
	for i = 1, 16 do
		optimism = optimism .. string.char( math.random( 1,255 ) )
	end
	
	syncvar.SetEntity( pl,"optimism", optimism )
	pl:UpdateCheckpoint( 0 )
end

function GM:PlayerSpawn( pl )
	umsg.Start( "player_spawn" )
		umsg.Short( pl:EntIndex() )
	umsg.End()
	pl:SetMana( 0 )
	pl:SetCarHealth( 100 )
	GAMEMODE:Buggify( pl )
end

function GM:PlayerDeath( pl, inflictor, attacker )

	SafeRemoveEntity( pl:GetRagdollEntity() )
	pl.NextSpawnTime = CurTime() + GAMEMODE.SpawnDelay
	
end

function GM:PlayerDeathThink( pl )

	if CurTime() > pl.NextSpawnTime then
		pl:Spawn()
	end
	
	pl:Spawn()

end

local toEndBlink = {}
function GM:SetEndBlink( entity, blink )
	toEndBlink[ entity ] = blink
end

function GM:EntityTakeDamage( ent, inflictor, attacker, amount, dmginfo )
	
	if not ( ent:IsNPC() or ent:IsPlayer() ) then return end
	
	if attacker:IsPlayer() then
		ent:SetBlinking( true )
		GAMEMODE:SetEndBlink( ent, CurTime() + 0.1 )
		ent.LastAttacker = attacker
	else
		ent:SetHealth( ent:Health() + amount )
	end
	
	if ( ent:Health() - amount <= 0 ) then
		if ValidEntity( ent.LastAttacker ) then
			ent.LastAttacker:SetCash( ent.LastAttacker:GetCash() + 50 )
		end
		ent:Remove()
	end

end

function GM:EntityRemoved( ent )

end

local lastThink = 0
local nextManaUpdate = 0
local manaFrameTime = CurTime()
function GM:Think()
	lastThink = CurTime()
	for k,v in pairs( ents.FindByClass( "player_melon" ) ) do
		v:UpdateMovement()
	end
	for _,pl in pairs( player.GetAll() ) do
		if pl:WaterLevel() > 0 then
			pl:Kill()
		end
		local check_automatic = pl:GetWeapon() and ( pl:GetWeapon().Automatic or ( not pl:GetWeapon().Automatic and not pl.LastInAttack ) )
		if check_automatic and pl.InAttack and pl:GetWeapon():CanAttack() then
			pl:GetWeapon():PrimaryAttack( pl.InAttackVictim )
		end
		if pl:GetWeapon() then
			pl:GetWeapon():Think()
		end
		pl.LastInAttack = pl.InAttack
	end
	if CurTime() > nextManaUpdate then
		local mana_regen = ( CurTime() - manaFrameTime )*10
		for _,pl in pairs( player.GetAll() ) do
			if pl:GetMana() < 100 then
				pl:SetMana( pl:GetMana() + mana_regen )
			end
			if pl:GetMana() > 100 then
				pl:SetMana( 100 )
			end
		end
		nextManaUpdate = CurTime() + 0.1
		manaFrameTime = CurTime()
	end
	for ent,EndBlink in pairs( toEndBlink ) do
		if not ValidEntity( ent ) then
			toEndBlink[ ent ] = nil
			continue
		end
		if CurTime() > EndBlink then
			ent:SetBlinking( false )
			toEndBlink[ ent ] = nil
		end
	end
end

function GM:SpawnManhacks( iCheckpoint )

	if GAMEMODE:IsWaiting() then return end

	local ent = GAMEMODE.Checkpoints[ iCheckpoint ]
	if not ValidEntity( ent ) then return end
	
	GAMEMODE.NextManhackRespawn[iCheckpoint] = GAMEMODE.NextManhackRespawn[iCheckpoint] or 0
	
	if CurTime() < GAMEMODE.NextManhackRespawn[iCheckpoint] then return end

	if #ents.FindByClass( "npc_manhack" ) > GAMEMODE.EstMaxManhacks then return end
	
	local pos = ent:LocalToWorld(ent:OBBCenter())
	
	/*for i=1,3 do
		local man_hack = ents.Create( "npc_manhack" )
		local man_pos = pos + Vector( math.cos( i/5 * math.pi )*15, math.cos( i/5 * math.pi )*15, math.sin( i/5 * math.pi )*15 )
		man_hack:SetPos( man_pos )
		man_hack:SetAngles( ( pos - man_pos ):Angle() )
		man_hack:Spawn()
	end*/
	
	GAMEMODE.NextManhackRespawn[iCheckpoint] = CurTime() + GAMEMODE.ManhackSpawnDelay

end

-- Called when the race starts
function GM:OnRaceStart()
	for _,pl in pairs( player.GetAll() ) do
		pl:SetCheckpoint( 0 )
		pl:UpdateCheckpoint( 2 )
		pl:SetLap( 1 )
	end
	MsgAll( "The race has started!" )
end

-- Called when a player passes a checkpoint.
function GM:OnPlayerCheckpoint( ply, iCheckpointNumber )
	MsgAll( "Player "..ply:GetName().." passed the "..tostring( iCheckpointNumber-1 )..STNDRD( iCheckpointNumber-1 ).." checkpoint of their "..tostring( ply:GetLap() )..STNDRD( ply:GetLap() ).." lap!" )
end

-- Called when a player completes a lap.
function GM:OnPlayerLap( ply, iLapNumber, fTime )
	ply:SetLap( iLapNumber + 1 )
	MsgAll( "Player "..ply:GetName().." completed their "..tostring( iLapNumber )..STNDRD( iLapNumber ).." lap in "..tostring( fTime ).." seconds!" )
end

-- Called when a player finishes the race.
function GM:OnPlayerFinish( ply, iPlace )
	MsgAll( "Player "..ply:GetName().." has finished the race in "..tostring( iPlace )..STNDRD( iPlace ).." place!" )
end

-- Called in addition to OnPlayerFinish when a player finishes the race in first place.
function GM:OnFirstPlayerFinish( ply )
	MsgAll( "Player "..ply:GetName().." has finished the race in first place!" )
end

-- Called in addition to OnPlayerFinish when a player finishes the race in last place.
function GM:OnLastPlayerFinish( ply )
	MsgAll( "Player "..ply:GetName().." has finished the race in last place!" )
end

function GM:CheckPlayReady()

	local pl_list = player.GetAll()
	local num = #pl_list
	local readies = 0
	for _,pl in pairs(pl_list) do
		
		if pl:IsReady() then
			readies = readies + 1
		end
		
	end
	
	if readies >= GAMEMODE.MinPlayers and readies >= math.ceil( (3/4) * num ) then
	
		for _,pl in pairs( player.GetAll() ) do
			pl:Spawn()
		end
		GAMEMODE:StartRace()
		syncvar.Set( "waiting", false )
	
	end

end

-- People are going to cheat anyways, so why not have the aim assist already inside the game? :)
concommand.Add( "+attack3", function( pl, cmd, arg )
	if tonumber(arg[1]) then
		if not syncvar.GetEntity( pl, "optimism" ) == arg[2] then return end
		pl.InAttackVictim = ents.GetByIndex(tonumber(arg[1]))
	else
		if not syncvar.GetEntity( pl, "optimism" ) == arg[1] then return end
		pl.InAttackVictim = nil
	end
	pl.InAttack = true
end )

concommand.Add( "-attack3", function( pl, cmd, arg )
	if not syncvar.GetEntity( pl, "optimism" ) == arg[1] then return end
	pl.InAttack = false
end )

concommand.Add( "ready", function( pl )
	pl:SetReady( true )
	GAMEMODE:CheckPlayReady()
end )

concommand.Add( "unready", function( pl )
	pl:SetReady( false )
end )

concommand.Add( "request_sync", function( pl )
	if ( pl.LastSync and CurTime() > pl.LastSync+30 ) or not pl.LastSync then
		syncvar.SyncPlayer( pl )
		pl.LastSync = CurTime()
	end
end )

concommand.Add( "real_angle", function( pl, cmd, arg )
	if not syncvar.GetEntity( pl, "optimism" ) == arg[2] then
		-- This player is hacking! Deal with him!
		return
	end
	pl.MoveDir = arg[1]
end )

concommand.Add( "create_block", function( pl, cmd, arg )
	if not syncvar.GetEntity( pl, "optimism" ) == arg[8] then
		-- This player is hacking! Deal with him!
		return
	end
	local cur_block = arg[1]
	local pos = Vector( arg[2],arg[3],arg[4] )
	local ang = Angle( arg[5],arg[6],arg[7] )
	
	MsgN( cur_block )
	if GAMEMODE:GetBlock( cur_block ) then
		MsgN( GAMEMODE:GetBlock( cur_block ) )
		GAMEMODE:GetBlock( cur_block ):CreateBlock( pl, pos, ang )
	end
end )

concommand.Add( "update_aim_angle", function( pl, cmd, arg )
	if not syncvar.GetEntity( pl, "optimism" ) == arg[4] then
		-- This player is hacking! Deal with him!
		return
	end
	pl:SetFakeAimVector( Vector( arg[1], arg[2], arg[3] ) )
end )
